package game;

import game.Audio.AudioResources;
import game.Rendering.GameCanvas;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.json.simple.parser.ParseException;

import javax.sound.sampled.*;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.List;


public class ConfigurationMenu {
    private static Pane root;
    private static GridPane subMenuPane;
    private static GridPane bottomButts;
    private static HashMap<String, TextField> textFields;
    private static HashMap<String, CheckBox> checkBoxes;
    private static HashMap<String, ComboBox> comboBoxes;
    private static HashMap<String, Slider> sliders;

    private static List<TextField> controls;
    private static List<KeyCode> usedKeys;
    private static boolean isVisible = false;

    private void addLabelAndText(GridPane grid, String labelText, String defaultText, boolean isNum, int row) {
        TextField tf = new TextField();
        if (defaultText != null) {
            tf.setText(defaultText);
        }
        if (isNum) {
            tf.textProperty().addListener((observable, oldValue, newValue) -> {
                if (!newValue.matches("\\d*")) {
                    tf.setText(newValue.replaceAll("[^\\d]", ""));
                }
            });
        }
        grid.add(new Label(labelText), 0, row);
        grid.add(tf, 1, row);
        textFields.put(labelText, tf);
    }

    private void addLabelAndCheck(GridPane grid, String labelText, int row, boolean checked) {
        GridPane aux = new GridPane();
        CheckBox cb = new CheckBox();
        cb.setSelected(checked);
        aux.add(cb, 0, 0);
        aux.add(new Label(labelText), 1, 0);
        grid.add(aux, 1, row);
        checkBoxes.put(labelText, cb);
    }

    private void addLabelAndCombo(GridPane grid, String labelText, ObservableList<String> options, int row, String selected) {
        ComboBox cb = new ComboBox(options);
        if (selected == null) cb.getSelectionModel().selectFirst();
        else cb.getSelectionModel().select(selected);
        grid.add(new Label(labelText), 0, row);
        grid.add(cb, 1, row);
        comboBoxes.put(labelText, cb);
    }

    private void addLabelAndSlider(GridPane grid, String labelText, int min, int max, double currentValue, int row) {
        grid.add(new Label(labelText), 0, row);
        Slider slider = new Slider();
        slider.setMin(min);
        slider.setMax(max);
        slider.setValue(currentValue);
        //TODO: Afinar visuals
        //slider.setShowTickLabels(true);
        //slider.setShowTickMarks(true);
        //slider.setMajorTickUnit(50);
        //slider.setMinorTickCount(5);
        slider.setBlockIncrement(10);
        grid.add(slider, 1, row);
        sliders.put(labelText, slider);
    }

    private void addControl(GridPane grid, String labelText, String defaultText, int row, int controlIndex) {
        TextField tf = new TextField();
        tf.setFont(new Font("Arial", 10));
        controls.add(tf);
        Label lb = new Label(labelText);
        if (defaultText != null) {
            tf.setText(defaultText);
        }
        tf.setOnKeyPressed(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent ke) {
                KeyCode k = ke.getCode();
                int idx = usedKeys.indexOf(k);
                if (idx >= 0) {
                    controls.get(idx).setText("");
                    usedKeys.set(idx, null);
                }
                usedKeys.set(controlIndex, ke.getCode());
                tf.setText(k.getName());
                lb.requestFocus();
            }
        });
        tf.focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> arg0, Boolean oldPropertyValue, Boolean newPropertyValue) {
                if (newPropertyValue) {
                    tf.setText("");
                } else {
                    if (tf.getText() == null || tf.getText().equals("")) {
                        KeyCode k = usedKeys.get(controlIndex);
                        //tf.setText(String.valueOf(controlIndex));
                        if (k != null) tf.setText(k.getName());
                    }
                }
            }
        });
        grid.add(lb, 0, row);
        grid.add(tf, 1, row);
        //textFields.add(tf);
    }

    private Tab playerTab() {
        GridPane grid = new GridPane();
        grid.setPadding(new Insets(10, 10, 10, 10));
        grid.setVgap(10);
        grid.setHgap(5);
        grid.setAlignment(Pos.TOP_LEFT);

        Tab t = new Tab("Player");
        int row = 0;
        addLabelAndText(grid, "Player Name:", Configuration.getNamePlayer(), false, row++);

        //addLabelAndCheck(grid, "Mark own Player", row++);
        addLabelAndCheck(grid, "Lefthanded Players", row++, Configuration.isLeftHanded());
        addLabelAndCheck(grid, "Recoil-Animations", row++, Configuration.isRecoilAnimation());
        //addLabelAndCheck(grid, "Wiggle-Animations", row++);

        t.setContent(grid);
        return t;
    }

    private Tab controlsTab() {
        Tab t = new Tab("Controls");
        GridPane grid = new GridPane();
        grid.setPadding(new Insets(10, 10, 10, 10));
        grid.setVgap(0);
        grid.setHgap(5);
        grid.setAlignment(Pos.TOP_LEFT);
        int row = 0, controlIndex = 0;
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getUp()));
        addControl(grid, "Move forward", Configuration.getInstance().getUp(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getDown()));
        addControl(grid, "Move back", Configuration.getInstance().getDown(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getLeft()));
        addControl(grid, "Strafe left", Configuration.getInstance().getLeft(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getRight()));
        addControl(grid, "Strafe right", Configuration.getInstance().getRight(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getReload()));
        addControl(grid, "Reload", Configuration.getInstance().getReload(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getPlant()));
        addControl(grid, "Plant", Configuration.getInstance().getPlant(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getPickUp()));
        addControl(grid, "Pickup", Configuration.getInstance().getPickUp(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getPrimaryWeapon()));
        addControl(grid, "Primary Weapon", Configuration.getInstance().getPrimaryWeapon(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getSecondaryWeapon()));
        addControl(grid, "Secondary Weapon", Configuration.getInstance().getSecondaryWeapon(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getKnife()));
        addControl(grid, "Knife", Configuration.getInstance().getKnife(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getalternateWeapon()));
        addControl(grid, "Alternate Weapon", Configuration.getInstance().getalternateWeapon(), row++, controlIndex++);
        usedKeys.add(KeyCode.getKeyCode(Configuration.getInstance().getStore()));
        addControl(grid, "Store", Configuration.getInstance().getStore(), row, controlIndex);

        t.setContent(grid);
        return t;
    }

    private Tab gameTab() {
        GridPane grid = new GridPane();
        grid.setPadding(new Insets(10, 10, 10, 10));
        grid.setVgap(10);
        grid.setHgap(5);
        grid.setAlignment(Pos.TOP_LEFT);
        Tab t = new Tab("Game");


        int row = 0;
        grid.add(new Label("Game Options:"), 0, row++);
        /*addLabelAndCheck(grid, "Auto-Help", row++);
        addLabelAndCheck(grid, "Switch to collected weapon", row++);
        addLabelAndCheck(grid, "Auto Scoreboard Screenshots", row++);
        addLabelAndCheck(grid, "Damage Direction Indicator", row++);*/
        addLabelAndCheck(grid, "Use Images for Death Notices", row++, Configuration.isDeathNoticesImages());
        addLabelAndCheck(grid, "Chat Communication", row++, Configuration.isChatCommunication());
        //addLabelAndCheck(grid, "Advanced Minimap", row);


        t.setContent(grid);

        return t;
    }

    private Tab graphicsTab() {
        GridPane grid = new GridPane();
        grid.setPadding(new Insets(10, 10, 10, 10));
        grid.setVgap(10);
        grid.setHgap(5);
        grid.setAlignment(Pos.TOP_LEFT);
        Tab t = new Tab("Graphics");

        int row = 0;
        //Resoluciones
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] gds = ge.getScreenDevices();
        Set<String> differentResolutions = new HashSet<String>();
        differentResolutions.add(Configuration.getResolution());
        int minHeight = 480;
        for (GraphicsDevice gd : gds) {
            DisplayMode dm = gd.getDisplayMode();
            int h = dm.getHeight();
            int w = dm.getWidth();
            differentResolutions.add(w + "x" + h);

            // Screen ratio
            int h2 = h / 2;
            int w2 = w / 2;
            while (h2 >= minHeight) {
                String resString = w2 + "x" + h2;
                if (!differentResolutions.contains(resString)) {
                    differentResolutions.add(resString);
                }

                h2 = (int) (h2 / 1.5);
                w2 = (int) (w2 / 1.5);
            }

            // 4:3 ratio
            h2 = h;
            w2 = h2 * 4 / 3;
            while (h2 >= minHeight) {
                String resString = w2 + "x" + h2;
                if (!differentResolutions.contains(resString)) {
                    differentResolutions.add(resString);
                }

                h2 = (int) (h2 / 1.5);
                w2 = h2 * 4 / 3;
            }
        }
        ObservableList<String> resolutionOptions = FXCollections.observableArrayList(differentResolutions);
        resolutionOptions.sort(new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                Integer w1 = Integer.parseInt(s1.split("x")[0]);
                Integer w2 = Integer.parseInt(s2.split("x")[0]);
                return w2.compareTo(w1);
            }
        });
        addLabelAndCombo(grid, "Resolution:", resolutionOptions, row++, Configuration.getResolution());
        addLabelAndCheck(grid, "Fullscreen", row++, Configuration.isIsFullscreen());
        //Sangre
        addLabelAndCheck(grid, "Blood", row++, Configuration.isBlood());
        //Sombras
        addLabelAndCheck(grid, "Shadows", row++, Configuration.isShadows());

        t.setContent(grid);

        return t;
    }

    private Tab soundTab() {
        GridPane grid = new GridPane();
        grid.setPadding(new Insets(10, 10, 10, 10));
        grid.setVgap(10);
        grid.setHgap(5);
        grid.setAlignment(Pos.TOP_LEFT);
        Tab t = new Tab("Sound");

        int row = 0;
        addLabelAndSlider(grid, "Global Volume:", 0, 100, Double.parseDouble(Configuration.getVolumeMaster()), row++);
        addLabelAndSlider(grid, "Game Volume:", 0, 100, Double.parseDouble(Configuration.getVolumeGame()), row++);
        addLabelAndSlider(grid, "Music Volume:", 0, 100, Double.parseDouble(Configuration.getVolumeMusic()), row++);
        addLabelAndSlider(grid, "Interface Volume:", 0, 100, Double.parseDouble(Configuration.getVolumeInterface()), row);

        t.setContent(grid);

        return t;
    }

    public ConfigurationMenu() {
        if (!isVisible) {
            root = GameCanvas.getPane();
            textFields = new HashMap<>();
            checkBoxes = new HashMap<>();
            comboBoxes = new HashMap<>();
            usedKeys = new ArrayList<>();
            controls = new ArrayList<>();
            sliders = new HashMap<>();
            int width = Helper.screenWidth(), height = Helper.screenHeight();
            Color lightG = new Color(55 / 255.0, 56 / 255.0, 60 / 255.0, 255 / 255.0);
            Background lightB = new Background(new BackgroundFill(lightG, new CornerRadii(10), null));
            Color darkG = new Color(50 / 255.0, 50 / 255.0, 50 / 255.0, 255 / 255.0);
            Background darkB = new Background(new BackgroundFill(darkG, null, null));
            subMenuPane = new GridPane();
            subMenuPane.setBackground(lightB);
            double windowWidth = 340;
            double windotHeight = 360;
            subMenuPane.setPrefSize(windowWidth, windotHeight);
            subMenuPane.setMaxSize(windowWidth, windotHeight);
            subMenuPane.setMinSize(windowWidth, windotHeight);
            subMenuPane.setLayoutX(width / 2.0 - windowWidth / 2.0);
            subMenuPane.setLayoutY(height / 2.0 - windotHeight / 2.0);
            root.getStylesheets().add(getClass().getClassLoader().getResource("styles/startMenu.css").toExternalForm());

            //Tabs
            TabPane tabs = new TabPane();
            tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
            tabs.getTabs().add(playerTab());
            tabs.getTabs().add(controlsTab());
            tabs.getTabs().add(gameTab());
            tabs.getTabs().add(graphicsTab());
            tabs.getTabs().add(soundTab());
            subMenuPane.add(tabs, 0, 0);

            Button start = new Button("Okay");
            start.setOnMouseClicked(e -> {
                try {
                    playButtonSound();
                } catch (Exception ex) {
                }
                saveConfig();
                destroyMenu();
            });
            Button cancel = new Button("Cancel");
            cancel.setOnMouseClicked(e -> {
                try {
                    playExitButtonSound();
                } catch (Exception ex) {
                }
                destroyMenu();
            });

            bottomButts = new GridPane();
            bottomButts.setAlignment(Pos.BOTTOM_RIGHT);
            bottomButts.setPadding(new Insets(0, 10, 20, 50));
            bottomButts.setHgap(20);
            bottomButts.add(start, 1, 0);
            bottomButts.add(cancel, 2, 0);
            subMenuPane.add(bottomButts, 0, 1);

            root.getChildren().add(subMenuPane);
            isVisible = true;
        }
    }

    public static void destroyMenu() {
        root.getChildren().remove(subMenuPane);
        root.getChildren().remove(bottomButts);
        isVisible = false;
    }

    public void playButtonSound() throws IOException, UnsupportedAudioFileException, LineUnavailableException {
        String fileName = "audio/wpn_moveselect.wav";
        InputStream bottomSound = AudioResources.class.getClassLoader().getResourceAsStream(fileName);
        AudioInputStream a = AudioSystem.getAudioInputStream(bottomSound);
        AudioFormat format = a.getFormat();
        DataLine.Info info = new DataLine.Info(Clip.class, format);
        Clip clipS = (Clip) AudioSystem.getLine(info);
        clipS.open(a);   // Open music file
        // Set volumen value
        FloatControl gainControl = (FloatControl) clipS.getControl(FloatControl.Type.MASTER_GAIN);
        double gain = Double.valueOf(Configuration.getInstance().getVolumeInterface()) * Double.valueOf(Configuration.getInstance().getVolumeMaster()) / 10000; // gain entre 0 y 1
        float dB = (float) (Math.log(gain) / Math.log(10.0) * 20.0);
        gainControl.setValue(dB);
        clipS.start();      // Play clip
    }

    public void playExitButtonSound() throws IOException, UnsupportedAudioFileException, LineUnavailableException {
        String fileName = "audio/wpn_select.wav";
        InputStream bottomSound = AudioResources.class.getClassLoader().getResourceAsStream(fileName);
        AudioInputStream a = AudioSystem.getAudioInputStream(bottomSound);
        AudioFormat format = a.getFormat();
        DataLine.Info info = new DataLine.Info(Clip.class, format);
        Clip clipS = (Clip) AudioSystem.getLine(info);
        clipS.open(a);   // Open music file
        // Set volumen value
        FloatControl gainControl = (FloatControl) clipS.getControl(FloatControl.Type.MASTER_GAIN);
        double gain = Double.valueOf(Configuration.getInstance().getVolumeInterface()) * Double.valueOf(Configuration.getInstance().getVolumeMaster()) / 10000; // gain entre 0 y 1
        float dB = (float) (Math.log(gain) / Math.log(10.0) * 20.0);
        gainControl.setValue(dB);
        clipS.start();      // Play clip
    }

    public void saveConfig() {
        double masterVol = sliders.get("Global Volume:").getValue();
        double gameVol = sliders.get("Game Volume:").getValue();
        double musicVol = sliders.get("Music Volume:").getValue();
        double interfaceVol = sliders.get("Interface Volume:").getValue();

        String playerName = textFields.get("Player Name:").getText();

        Configuration.getInstance().setVolumeMaster(String.valueOf(masterVol));
        Configuration.getInstance().setVolumeGame(String.valueOf(gameVol));
        Configuration.getInstance().setVolumeMusic(String.valueOf(musicVol));
        Configuration.getInstance().setVolumeInterface(String.valueOf(interfaceVol));
        Configuration.getInstance().setNamePlayer(playerName);

        try {
            Configuration.getInstance().changeConfiguration("Configuration", "Volume-master", String.valueOf((int) masterVol));
            Configuration.getInstance().changeConfiguration("Configuration", "Volume-music", String.valueOf((int) musicVol));
            Configuration.getInstance().changeConfiguration("Configuration", "Volume-game", String.valueOf((int) gameVol));
            Configuration.getInstance().changeConfiguration("Configuration", "Volume-interface", String.valueOf((int) interfaceVol));
            Configuration.getInstance().changeConfiguration("Configuration", "Name-player", playerName);
        } catch (Exception e) {

        }

        int idx = 0;
        KeyCode up = usedKeys.get(idx++);                    // Up move key
        KeyCode down = usedKeys.get(idx++);                    // Down move key
        KeyCode left = usedKeys.get(idx++);                    // Left move key
        KeyCode right = usedKeys.get(idx++);                // Right move key
        KeyCode reload = usedKeys.get(idx++);                // Reload weapon key
        KeyCode plant = usedKeys.get(idx++);                // Plant weapon key
        KeyCode pickUp = usedKeys.get(idx++);                // Pick up key
        KeyCode primaryWeapon = usedKeys.get(idx++);        // Switch key to primary weapon
        KeyCode secondaryWeapon = usedKeys.get(idx++);        // Switch key to secondary weapon
        KeyCode knife = usedKeys.get(idx++);                // Switch key to knife weapon
        KeyCode alternateWeapon = usedKeys.get(idx++);        // Switch key to the last weapon selected
        KeyCode store = usedKeys.get(idx);

        Configuration.getInstance().setUp(up.getName());
        Configuration.getInstance().setDown(down.getName());
        Configuration.getInstance().setLeft(left.getName());
        Configuration.getInstance().setRight(right.getName());
        Configuration.getInstance().setReload(reload.getName());
        Configuration.getInstance().setPlant(plant.getName());
        Configuration.getInstance().setPickUp(pickUp.getName());
        Configuration.getInstance().setPrimaryWeapon(primaryWeapon.getName());
        Configuration.getInstance().setSecondaryWeapon(secondaryWeapon.getName());
        Configuration.getInstance().setKnife(knife.getName());
        Configuration.getInstance().setalternateWeapon(alternateWeapon.getName());
        Configuration.getInstance().setStore(store.getName());

        try {
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "up", up.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "down", down.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "left", left.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "right", right.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "reload", reload.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "plant", plant.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "pickUp", pickUp.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "primaryWeapon", primaryWeapon.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "secondaryWeapon", secondaryWeapon.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "knife", knife.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "alternateWeapon", alternateWeapon.getName());
            Configuration.getInstance().changeConfiguration("KeyboardConfiguration", "store", store.getName());

        } catch (Exception e) {

        }

        boolean leftHanded = checkBoxes.get("Lefthanded Players").isSelected();
        boolean recoilAnimation = checkBoxes.get("Recoil-Animations").isSelected();
        boolean deathNoticesImages = checkBoxes.get("Use Images for Death Notices").isSelected();
        boolean chatCommunication = checkBoxes.get("Chat Communication").isSelected();
        boolean blood = checkBoxes.get("Blood").isSelected();
        boolean shadows = checkBoxes.get("Shadows").isSelected();
        boolean fullScreen = checkBoxes.get("Fullscreen").isSelected();

        Configuration.setLeftHanded(leftHanded);
        Configuration.setRecoilAnimation(recoilAnimation);
        Configuration.setDeathNoticesImages(deathNoticesImages);
        Configuration.setChatCommunication(chatCommunication);
        Configuration.setBlood(blood);
        Configuration.setShadows(shadows);
        Configuration.setIsFullscreen(fullScreen);
        try {
            Configuration.getInstance().changeConfiguration("Configuration", "leftHanded", String.valueOf(leftHanded));
            Configuration.getInstance().changeConfiguration("Configuration", "recoilAnimation", String.valueOf(recoilAnimation));
            Configuration.getInstance().changeConfiguration("Configuration", "deathNoticesImages", String.valueOf(deathNoticesImages));
            Configuration.getInstance().changeConfiguration("Configuration", "chatCommunication", String.valueOf(chatCommunication));
            Configuration.getInstance().changeConfiguration("Configuration", "blood", String.valueOf(blood));
            Configuration.getInstance().changeConfiguration("Configuration", "shadows", String.valueOf(shadows));
            Configuration.getInstance().changeConfiguration("Configuration", "fullScreen", String.valueOf(fullScreen));
        } catch (Exception e) {

        }


        String resolution = comboBoxes.get("Resolution:").getSelectionModel().getSelectedItem().toString();
        Configuration.setResolution(resolution);
        try {
            Configuration.getInstance().changeConfiguration("Configuration", "Resolution", resolution);
        } catch (IOException | ParseException e) {
            e.printStackTrace();
        }
    }

    public static boolean isVisible() {
        return isVisible;
    }
}
